package com.teacher.logs;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.teacher.common.JwtTokenUtil;
import com.teacher.logs.mapper.OptionLogMapper;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.NamedThreadLocal;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Map;

/**
 * @Author: zfm
 * @Date: 2019/11/19 08:55
 */
@Component
@Aspect
@Slf4j
public class LogAop {

    private static final Logger LOGGER = LoggerFactory.getLogger(LogAop.class);
    private static final ThreadLocal<Date> BEGIN_TIME_THREAD_LOCAL = new NamedThreadLocal<>("thread local date");

    private static final ThreadLocal<OptionLog> CURRENT_ACCOUNT = new NamedThreadLocal<>("thread local account");

    @Autowired
    private HttpServletRequest request;

    @Autowired
    @Qualifier("taskExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private OptionLogMapper optionLogMapper;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    /**
     * 前置通知
     */
    @Pointcut("execution(public * com.teacher.*.*.*(..))")
    public void cut() {
        System.out.println(11);
    }

    @Before(value = "execution(public * com.teacher.*.controller.*.*(..))")
    public void before(JoinPoint joinPoint) {
        OptionLog cmsOptionLog = new OptionLog();
        Date beginTime = new Date();
        BEGIN_TIME_THREAD_LOCAL.set(beginTime);
        // 读取请求头的信息再获取用户名
        String authHeader = request.getHeader(this.tokenHeader);
        String username = "";
        if (authHeader != null) {
            username = jwtTokenUtil.getUsernameByRequest(request);
        }
        cmsOptionLog.setUsername(username);
        CURRENT_ACCOUNT.set(cmsOptionLog);
    }


    @After("execution(public * com.teacher.*.controller.*.*(..))")
    public void afterAdvice(JoinPoint joinPoint) {
        // 日志类型
        Integer logType = 10;
        // 请求者IP
        String address = getIpAddr(request);
        // 请求的uri
        String requestUri = request.getRequestURI().toString();
        // 请求的方法类型（post, get, put, delete, ...）
        String reqMethod = request.getMethod();
        // 获取请求的参数
        String paramsStr = "";
        if (RequestMethod.POST.name().equals(reqMethod) ||
                RequestMethod.PUT.name().equals(reqMethod) ||
                RequestMethod.DELETE.name().equals(reqMethod)) {
            Object[] objects = joinPoint.getArgs();
            paramsStr = argsArrayToString(objects);
        } else {
            Map<?, ?> map = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            paramsStr = JSONUtil.toJsonStr(map);
        }
        // 获取swagger 注解中的参数
        String title = "";
        Signature signature = joinPoint.getSignature();
        Method method = ((MethodSignature) signature).getMethod();
        if (method.isAnnotationPresent(ApiOperation.class)) {
            ApiOperation log = method.getAnnotation(ApiOperation.class);
            title = log.value();
        }
        // 打印JVM信息
        long beginTime = BEGIN_TIME_THREAD_LOCAL.get().getTime();
        long endTime = System.currentTimeMillis();
        OptionLog cmsOptionLog = CURRENT_ACCOUNT.get();
        cmsOptionLog.setUrl(requestUri);
        cmsOptionLog.setAddress(address);
        cmsOptionLog.setLogType(logType);
        cmsOptionLog.setReqMethod(reqMethod);
        cmsOptionLog.setReqParam(paramsStr);
        cmsOptionLog.setTimeout(Long.toString(endTime - beginTime));
        cmsOptionLog.setTitle(title);
        LOGGER.info("——————————————————"+title+"————————————————");
        LOGGER.info("openId:"+cmsOptionLog.getUsername());
        LOGGER.info("地址:"+requestUri);
        LOGGER.info("类型:"+reqMethod);
        LOGGER.info("花费时间:"+cmsOptionLog.getTimeout()+"毫秒");
        LOGGER.info("传入信息:"+paramsStr);


    }

    /**
     * 请求参数拼装
     *
     * @param paramsArray
     * @return 请求参数
     */
    private String argsArrayToString(Object[] paramsArray) {
        if (paramsArray == null || paramsArray.length <= 0) {
            return "";
        }
        StringBuilder params = new StringBuilder();
        for (Object object : paramsArray) {
            if (object instanceof HttpServletRequest ||
                    object instanceof HttpServletResponse ||
                    object instanceof MultipartFile) {
                continue;
            }
            String jsonObj = JSONUtil.toJsonStr(object);
            if (!StringUtils.isEmpty(jsonObj)) {
                params.append(jsonObj).append(" ");
            }
        }
        return params.toString().trim();
    }

    private String getIpAddr(HttpServletRequest request) {
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if ("127.0.0.1".equals(ipAddress)) {
                    // 根据网卡取本机配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                    ipAddress = inet.getHostAddress();
                }
            }
            // 对于通过多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割
            // "***.***.***.***".length()
            if (ipAddress != null && ipAddress.length() > 15) {
                // = 15
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress = "";
        }
        return ipAddress;
    }

    @AfterReturning(value = "execution(public * com.teacher.*.controller.*.*(..))", returning = "res")
    public void afterReturn(Object res) {
        OptionLog cmsOptionLog = CURRENT_ACCOUNT.get();
        cmsOptionLog.setResParam(JSONUtil.toJsonStr(res));
        String s = JSONUtil.formatJsonStr(cmsOptionLog.getResParam());
        LOGGER.info("返回信息"+s);
        LOGGER.info("————————————————————————————接口日志结束——————————————————————————————————————");
        threadPoolTaskExecutor.execute(new SaveLogThread(cmsOptionLog, optionLogMapper));
    }

    /**
     * 保存日志
     */
    private static class SaveLogThread implements Runnable {

        private OptionLog cmsOptionLog;
        private OptionLogMapper cmsOptionLogMapper;

        SaveLogThread(OptionLog cmsOptionLog, OptionLogMapper optionLogMapper) {
            super();
            this.cmsOptionLog = cmsOptionLog;
            this.cmsOptionLogMapper = optionLogMapper;
        }

        @Override
        public void run() {
            try {
                //LOGGER.info("接口日志:"+BeanUtil.beanToMap(cmsOptionLog));
                cmsOptionLogMapper.insert(cmsOptionLog);
                CURRENT_ACCOUNT.remove();
                BEGIN_TIME_THREAD_LOCAL.remove();
            } catch (Exception e) {
                e.printStackTrace();
                log.error("更新日志出错", e);
            }
        }
    }

    @AfterThrowing(value = "execution(public * com.teacher.*controller.*(..))", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Throwable e) {
        OptionLog cmsOptionLog = CURRENT_ACCOUNT.get();
        cmsOptionLog.setLogType(20);
        cmsOptionLog.setException(e.toString());
        threadPoolTaskExecutor.execute(new SaveLogThread(cmsOptionLog, optionLogMapper));
    }
}
